/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14.model;

import java.io.Serializable;

/**
 * This class represents the role of the server.
 * <p>
 * 
 * It stores information about:
 * - Who am I? (my server Id)
 * - Am I the leader? (is the leader me)
 * - Who is the leader? (which server id)
 * - Am I doing elections? (it has been started? or it has been cancelled? whats going on?)
 * <p>
 * 
 * @author drodenas
 */
public final class Role implements Serializable {
	private static final long serialVersionUID = -5602857674012891176L;
	protected Role() { myId = null; }

	private static final Logger log = Logger.get("ModelRole");
    
    private final ServerId myId;
    private ServerId leaderId;
    private boolean doingElections;

    Role(ServerId myId) {
        this.myId = myId;
    }
    
    /**
     * The role of the server becomes leader.
     * <p>
     * 
     * You should call this when timerElectionEnd has been reached
     * and you want to become the leader.
     * Since this point you will be leader, and leader will point to you.
     */
    public void becomeLeader() {
        log.debug(myId, "becomeLeader() leader:"+leaderId+"->"+myId);
        
        leaderId = myId;        
    }
    
    /**
     * It annotates that you are starting elections.
     * <p>
     * 
     * You should call it when you begin elections,
     * usually when timerElectionBegin. 
     * This will annotate that you are in a valid period of elections now.
     */
    public void beginElections() {
        log.debug(myId, "beginElections() elections:"+doingElections+"->true");
        
        doingElections = true;
    }
    
    /**
     * It cancels elections
     * <p>
     * 
     * You should call it when you receive an abort message.
     * By doing this you should prevent to become leader.
     * It usually should happen before timerElectionEnd.
     * <p>
     * 
     * Ok, its exactly the same that endElections, 
     * but it has a different meaning, and makes code more readable and debug messages nicer.
     */
    public void cancelElections() {
        log.debug(myId, "cancelElections() elections:"+doingElections+"->false");
        
        doingElections = false;
    }

    /**
     * It ends elections.
     * <p>
     * 
     * You should call it when you receive timerElectionEnd
     * and you want to finish elections, either you are winner
     * or not.
     * 
     * Ok, its exactly the same that cancelElections, 
     * but it has a different meaning, and makes code more readable and debug messages nicer.
     */
    public void endElections() {
        log.debug(myId, "endElections() elections:"+doingElections+"->false");
        
        doingElections = false;
    }

    /**
     * Gets who is currently the leader.
     * <p>
     * 
     * This method can return null if no leader is known, 
     * can return any other leader (even leader with
     * lower ids if they are in fact leader), and
     * itself it itself is the leader.
     * 
     * @return the current known leader id or null if it is unknown
     */
    public ServerId getLeader() {
        return leaderId;
    }

    /**
     * Obtains my Id.
     * <p>
     * 
     * This method is usefull to fill the from field of
     * outgoing messages and also to check if any of the existing
     * servers in the cluster list of servers is you to avoid
     * to send a message to yourself.
     *  
     * @return your server id which never is null
     */
    public ServerId getMyId() {
        return myId;
    }
    
    /**
     * Ask if there are elections currently.
     * <p>
     * 
     * You should call to this method to know if there are any valid elections going on.
     * This should be done when timerElectionEnd is called and you want to know
     * if you can become leader. If elections were cancelled previously it will return false.
     * You should call beginElections when you start elections.
     * 
     * @return true if your are doing elections, or false if you cancelled it, it has not started, or it has already ended
     */
    public boolean isDoingElections() {
        return doingElections;
    }

    /**
     * Method to know if you are a follower.
     * <p>
     * You will use it to know that you are a follower so you should to act as a follower.
     * 
     * @return true if leader is known and you are not the leader, false if leader is unknown or if you are the leader
     */
    public boolean isFollower() {
        return isLeaderKnown() && !isLeader();
    }
    
    /**
     * Method to know if you are the leader.
     * <p>
     * 
     * You should use this method to know that you are the leader so you should act as a leader.
     * 
     * @return true if the leader is known and you are leader, false if leader is unknown, or is another server 
     */
    public boolean isLeader() {
        return isLeaderKnown() && leaderId.equals(myId);
    }
    
    /**
     * Method to know if you are the leader.
     * <p>
     * 
     * This method is usefull to know when you are lost and usually when you should start a
     * leader election when you receive a seat operation.
     * 
     * @return true if the leader is known, does not mater if you are the leader or not
     */
    public boolean isLeaderKnown() {
        return leaderId != null;
    }

    /**
     * Saves the leader.
     * <p>
     * 
     * After calling this function you should become a follower.
     * <p>
     * 
     * You should use it when you receive an recover message from the current
     * leader and you want to save that this other server is the leader.
     * You should no use it to become leader, use {@link #becomeLeader()} instead.
     * I you want to unset leader, for example you start an elections because
     * leader has not answered, you should {@link #removeLeader()}.
     * 
     * @param leader the current leader, it should not be you neither null
     * @see #becomeLeader()
     * @see #removeLeader()
     */
    public void setLeader(ServerId leader) {
        if (leader == null) throw new NullPointerException("Illegal argument: leader cannot be null");
        
        log.debug(myId, "setLeader() leader:"+leaderId+"->"+leader);
        this.leaderId = leader;
    }

    /**
     * Removes the leader.
     * <p>
     * 
     * After invoking this function you will stepdown if you are the leader
     * or you will become "lost" ({@link #isLeaderKnown() == false) so you need
     * to start an elections.
     * <p>
     * 
     * You should call this funcion when timeout received to timeoutElectionStart
     * usually because no other leader is found or valid.
     */
    public void removeLeader() {
        log.debug(myId, "removeLeader() leader:"+leaderId+"->null");

        this.leaderId = null;
    }

    @Override
    public String toString() {
        return "Role{" + "myId=" + myId + ", leaderId=" + leaderId + ", doingElections=" + doingElections + '}';
    }
}
